<script lang="ts">
    import { useFormItemContext } from "../FormItem/FormItem.svelte";
    import { ImagePreview } from "../ImagePreview";
    import { createUniqueId } from "../utils/createUniqueId";
    import { useClassList } from "../utils/useClassList";
    import List from "./List.svelte";
    import PictureList from "./PictureList.svelte";
    import type { UploadProps } from "./Upload";
    import ajax from "./ajax";

    let { value = $bindable([]), ...props }: UploadProps = $props();
    const ctx = useFormItemContext();
    let dragOver = $state(false);
    let visible = $state(false);
    let input: any;
    const format = props.format ?? [];
    const type = props.type ?? "select";
    const showClear = props.showClear ?? true;
    const showRetry = props.showRetry ?? true;
    const fileMap: Record<string | number, any> = {};
    value?.forEach((item) => {
        fileMap[item.uid] = item;
    });
    const store = $state({
        fileList: value,
        previewUrl: "",
        fileMap: fileMap,
    });
    const name = props.name ?? "file";

    const classList = $derived(
        useClassList(props, "cm-upload", {
            "cm-upload-select": type === "select",
            "cm-upload-drag": type === "drag",
            "cm-upload-dragOver": type === "drag" && dragOver,
        })
    );

    const handleChange = (e: any) => {
        const files = e.target.files;
        if (!files) {
            return;
        }
        uploadFiles(files);
        input.value = null;
    };

    const uploadFiles = (files: FileList) => {
        let postFiles: File[] = Array.prototype.slice.call(files);
        if (!props.multiple) postFiles = postFiles.slice(0, 1);
        if (postFiles.length === 0) return;
        postFiles.forEach((file: File) => {
            upload(file);
        });
    };

    const upload = async (file: File) => {
        let ret;
        if (props.beforeUpload) {
            ret = await props.beforeUpload(file);
        }
        if (ret === false) {
            return;
        }
        post(file);
    };

    const post = (file: any) => {
        if (props.maxCount && props.maxCount > 1 && store.fileList.length >= props.maxCount) {
            props.onexceededcount?.(file, store.fileList);
            return false;
        }
        // check format
        if (format.length) {
            const fileFormat = file.name.split(".").pop().toLocaleLowerCase();
            const checked = format.some((item) => item.toLocaleLowerCase() === fileFormat);
            if (!checked) {
                props.onformaterror?.(file, store.fileList);
                return false;
            }
        }
        // check maxSize
        if (props.maxSize) {
            if (file.size > props.maxSize * 1024) {
                props.onexceededsize?.(file, store.fileList);
                return false;
            }
        }
        const _file = handleStart(file);
        if (props.customRequest) {
            props.customRequest({
                file: file,
                onProgress: (e: any) => {
                    handleProgress(e, _file);
                },
                onSuccess: (res: any) => {
                    handleSuccess(res, _file);
                },
                onError: (err: any, response: any) => {
                    handleError(err, response, _file);
                },
            });
        } else {
            if (props.action) {
                ajax({
                    headers: props.headers,
                    withCredentials: props.withCredentials,
                    file: file,
                    data: props.data,
                    filename: name,
                    action: props.action,
                    onProgress: (e: any) => {
                        handleProgress(e, _file);
                    },
                    onSuccess: (res: any) => {
                        handleSuccess(res, _file);
                    },
                    onError: (err: any, response: any) => {
                        handleError(err, response, _file);
                    },
                });
            } else {
                handleError(new Error("action is required"), null, _file);
            }
        }
    };

    const handleStart = (file: any) => {
        const _file = {
            uid: createUniqueId(),
            originFile: file,
            name: file.name,
            size: file.size,
            type: file.type,
            percentage: 0,
            status: "uploading",
            showProgress: true,
            url: "",
        };
        store.fileMap[_file.uid] = _file;
        // 本地预览
        if (_file.type.startsWith("image/")) {
            _file.url = URL.createObjectURL(file);
        }
        if (props.maxCount === 1) {
            store.fileList = [_file];
            value = [_file];
            ctx?.onFieldChange();
        } else {
            const originList = [...store.fileList];
            store.fileList = [...originList, _file];
            value = [...originList, _file];
            ctx?.onFieldChange();
        }
        return _file;
    };

    const getFile = (file: any): any => {
        const fileList = store.fileList;
        let target;
        fileList.every((item) => {
            target = file.uid === item.uid ? item : null;
            return !target;
        });
        return target;
    };

    /**
     * 上传进度
     * @param e
     * @param file
     */
    const handleProgress = (e: any, file: any) => {
        const _file = getFile(file);
        if (!_file) {
            return;
        }
        props.onprogress?.(e, _file, store.fileList);
        _file.percentage = e.percent || 0;
    };

    const handleSuccess = (res: any, file: any) => {
        const _file: any = getFile(file);
        if (_file) {
            _file.status = "finished";
            _file.response = res;
            if (props.getFileUrl) {
                _file.url = props.getFileUrl(res, _file);
            }
            props.onsuccess?.(res, _file, store.fileList);
            props.onchange?.(_file, store.fileList);
            setTimeout(() => {
                _file.showProgress = false;
            }, 1000);
        }
    };

    const handleError = (err: any, response: any, file: any) => {
        const _file = getFile(file);
        _file.status = "fail";
        props.onerror?.(err, response, file);
        props.onchange?.(_file, store.fileList);
    };

    const handleRemove = (file: any) => {
        store.fileList = store.fileList.filter((item: any) => item.uid !== file.uid);
        delete store.fileMap[file.uid];
        props.onremove?.(file, store.fileList);
        value = store.fileList;
        ctx?.onFieldChange();
    };

    const handlePreview = (file: any) => {
        if (file.status === "finished") {
            store.previewUrl = file.url;
            visible = true;
            props.onpreview?.(file);
        }
    };

    export const clearFiles = () => {
        const oldFileList = $state.snapshot(store.fileList);
        store.fileMap = {};
        store.fileList = [];
        props.onclear?.(oldFileList);
        value = [];
        ctx?.onFieldChange();
    };

    const handleClick = () => {
        if (props.disabled) {
            return;
        }
        input.click();
    };

    /**
     * 重试
     * @param item
     */
    const onRetry = (item: any) => {
        const file: any = store.fileMap[item.uid];
        if (file) {
            if (props.customRequest) {
                props.customRequest({
                    file: file.originFile,
                    onProgress: (e: any) => {
                        handleProgress(e, file);
                    },
                    onSuccess: (res: any) => {
                        handleSuccess(res, file);
                    },
                    onError: (err: any, response: any) => {
                        handleError(err, response, file);
                    },
                });
            } else {
                if (props.action) {
                    ajax({
                        headers: props.headers,
                        withCredentials: props.withCredentials,
                        file: file.originFile,
                        data: props.data,
                        filename: name,
                        action: props.action,
                        onProgress: (e: any) => {
                            handleProgress(e, file);
                        },
                        onSuccess: (res: any) => {
                            handleSuccess(res, file);
                        },
                        onError: (err: any, response: any) => {
                            handleError(err, response, file);
                        },
                    });
                } else {
                    handleError(new Error("action is required"), null, file);
                }
            }
        }
    };

    const onDrop = (e: any) => {
        e.preventDefault && e.preventDefault();
        dragOver = false;
        if (props.disabled) return;
        uploadFiles(e.dataTransfer.files);
    };

    const handlePaste = (e: any) => {
        if (props.disabled) return;
        if (props.paste) {
            uploadFiles(e.clipboardData.files);
        }
    };

    const onDragOver = (e: any) => {
        e.preventDefault && e.preventDefault();
        dragOver = true;
    };

    const onDragLeave = (e: any) => {
        e.preventDefault && e.preventDefault();
        dragOver = false;
    };
</script>

<div class={classList} style={props.style}>
    <input class="cm-upload-input" bind:this={input} type="file" onchange={handleChange} multiple={props.multiple} webkitdirectory={props.webkitdirectory} accept={props.accept} />
    {#if props.listType === "picture"}
        <PictureList files={store.fileList} onRemove={handleRemove} onPreview={handlePreview} onClick={handleClick}>
            {@render props.children?.()}
        </PictureList>
    {/if}
    {#if props.listType !== "picture"}
        <!-- svelte-ignore a11y_click_events_have_key_events -->
        <!-- svelte-ignore a11y_no_static_element_interactions -->
        <div class="cm-upload-out" onclick={handleClick} ondrop={onDrop} onpaste={handlePaste} ondragover={onDragOver} ondragleave={onDragLeave}>
            {@render props.children?.()}
        </div>
        <List
            maxCount={props.maxCount}
            {showClear}
            {showRetry}
            {store}
            renderFileOperation={props.renderFileOperation}
            onRemove={handleRemove}
            onPreview={handlePreview}
            onClear={clearFiles}
            {onRetry}
        />
    {/if}
    <ImagePreview previewList={[store.previewUrl]} bind:visible />
</div>
